package com.example.coroutines

import kotlinx.coroutines.*
import org.junit.Test


/**
 * 协程上下文异常测试
 * 协程构建器有两种形式：自动异常传播(launch和actor),
 * 向用户暴露异常（async和produce）。当这些构建器用于创建一个根协程时
 * 前者这类构建器，异常会在它发生的第一时间被抛出，而后者则依赖用户最终消费异常。
 * 例如通过await和receive。
 *
 * 什么是根协程？
 * 该协程不是另一个协程的子协程。
 *
 * @author zhouronghua
 * @time 2021/12/31 8:43 上午
 */
class CoroutineExceptionTest {

    /**
     * 测试协程异常
     *
     * @author zhouronghua
     * @time 2021/12/31 12:42 下午
     */
    @Test
    fun test_coroutine_exception() = runBlocking {
        val coroutineExceptionHandler = CoroutineExceptionHandler { _, throwable ->
            // 捕获异常进行处理
            println("Caught $throwable")
        }
        val scope = CoroutineScope(
            Job() + Dispatchers.Main + coroutineExceptionHandler
        )
        val job = scope.launch(Dispatchers.IO) {
            // 启动一个新协程
            println("child coroutine: " + coroutineContext)
            // 抛出异常
            throw  Exception("子协程处理异常")
        }
        job.join()
    }

    /**
     * 通过异常处理Handler捕捉异常
     *
     * @author zhouronghua
     * @time 2021/12/31 12:50 下午
     */
    @Test
    fun test_coroutine_user_exception(): Unit = runBlocking {
        val coroutineExceptionHandler = CoroutineExceptionHandler { _, throwable ->
            // 捕获异常进行处理
            println("Caught $throwable")
        }
        val scope = CoroutineScope(
            Job() + Dispatchers.Main + coroutineExceptionHandler
        )
        val deferred = scope.async(Dispatchers.IO) {
            // 启动一个新协程
            println("child coroutine: " + coroutineContext)
            // 抛出异常
            throw  Exception("子协程处理异常")
        }
        delay(1000)
        try {
            println("子协程任务开始被消费....")
            // 异步协程任务执行(被消费时才会抛出异常)
            deferred.await()
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    /**
     * 测试根协程异常捕捉
     *
     * @author zhouronghua
     * @time 2022/1/1 9:59 上午
     */
    @Test
    fun test_root_coroutine_exception_catch(): Unit = runBlocking {
        // 根协程任务抛出异常时立即传播
        val job = GlobalScope.launch {
            // 捕获到异常会立即终止
            try {
                throw IndexOutOfBoundsException("自动传播异常")
            } catch (e: Exception) {
                e.printStackTrace()
                println("捕获到异常 Caught: $e")
            }
        }
        job.join()

        // 测试用户传播异常(用户真正执行到的时候才抛出异常)
        val deferred = GlobalScope.async {
            // 抛出异常系统不会自动处理，需要用户捕获
            throw ArrayStoreException("向用户暴露的异常")
        }
        println("异步启动协程")
        // 向用户暴露的异常只有在await处理的时候才能捕获到
        try {
            // 异步协程真正消费时才会抛出异常
            deferred.await()
        } catch (e: Exception) {
            println("捕获到异常 Caught: $e")
        }
    }

    /**
     * 非根协程产生的异常总是会被传播
     * 说明：非根协程产生的异常，会自动传播给父协程。
     *
     * @author zhouronghua
     * @time 2022/1/1 10:23 上午
     */
    @Test
    fun test_child_coroutine_exception() = runBlocking {
        val scope = CoroutineScope(Job())
        val job = scope.launch {
            // 子协程任务
            println("子协程（非根协程）启动一个异步协程任务")
            async {
                println("子协程抛出异常")
                // 异步任务抛出的异常会自动向父协程传播，而不需要真正调用await才传播
                throw IllegalAccessError("访问权限不足异常")
            }
        }
        job.join()
    }

    /**
     * 异常传播特性
     * 1）当一个子协程由于异常而失败时，会自动传播异常给父级；
     * 2）父级接收到异常，会进行以下处理：
     * *取消它自己的自己
     * *取消他自己
     * *将异常传播给他的父级
     *
     * @author zhouronghua
     * @time 2022/1/1 10:37 上午
     */
    @Test
    fun test_coroutine_exception_spread() = runBlocking {
        val coroutineExceptionHandler = CoroutineExceptionHandler { _, throwable ->
            // 捕获异常进行处理
            println("Caught $throwable")
        }
        val scope = CoroutineScope(Job() + coroutineExceptionHandler)
        val job = scope.launch {
            // 子协程任务
            println("子协程（非根协程）启动一个协程任务")
            val child = launch {
                println("子协程1（非根协程）启动一个协程任务")
                launch {
                    try {
                        println("子协程2任务执行${coroutineContext[CoroutineName]}")
                        delay(1000)
                    } catch (e: Exception) {
                        e.printStackTrace()
                        println("子协程2任务执行异常 ${coroutineContext[CoroutineName]}")
                    } finally {
                        withContext(NonCancellable) {
                            println("子协程2任务执行完成 ${coroutineContext[CoroutineName]}")
                        }
                    }
                }
                async {
                    println("子协程2抛出异常${coroutineContext[CoroutineName]}")
                    // 异步任务抛出的异常会自动向父协程传播，而不需要真正调用await才传播
                    throw IllegalAccessError("访问权限不足异常")
                }
            }
            child.join()
        }
        job.join()
    }

}